#pragma once

#include "OrionFlames.h"
#include "IfsRenderer.h"
#include "PresetList.h"

namespace OrionFlames
{
class OFAPI IfsAnimator
{
public:
	IfsAnimator()
	{
		//Timing.
		m_CurrentFrameStartMsec = 0;

		//Rotation.
		m_RotateX = true;
		m_RotateY = true;
		m_RotateO = false;
		m_XRotationRate = 0.03f;
		m_YRotationRate = 0.03f;
		m_ORotationRate = 0.03f;
		m_XRotationAngle = 0.0f;
		m_YRotationAngle = 0.0f;
		m_ORotationAngle = 0.0f;
		m_LastXRotationMsec = m_LastYRotationMsec = m_LastORotationMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME);

		//Color.
		m_VaryColor = false;
		m_LastColorVaryMsec = 0;
		m_ColorTimePerXform = 90;
		m_ColorVaryRate = 0.005f;
		m_ColorMax = 0.85f;
		m_ColorMin = 0.20f;
		//m_Colorful;
		m_ColorVaryDirection = 1.0f;

		//Exposure.
		m_VaryExposure = false;
		m_LastExposureVaryMsec = 0;
		m_ExposureTimePerXform = 80;
		m_ExposureMax;
		m_ExposureMin;
		m_ExposureVaryRate = 0.01f;

		//Preset.
		m_NewPreset = true;//We want to set it up the first time through.
		m_SetNewPreset = false;
		m_CurrentPresetIndex = 0;
		m_CurrentPreset = new Preset(*(PresetList::Get(m_CurrentPresetIndex)));
		m_OriginalPreset = new Preset(*(PresetList::Get(m_CurrentPresetIndex)));

		//This must be done, but we can't do it here.
		/*oglCameraInit();*/

		camera_orient.x = vec3(1, 0, 0);
		camera_orient.y = vec3(0, 1, 0);
		camera_orient.z = vec3(0, 0, 1);
		camera=vec3(0.0, 0.0, +3);
	}

	~IfsAnimator()
	{
		Delete(m_CurrentPreset);
		Delete(m_OriginalPreset);
	}

	void Draw(int width, int height)
	{
		//glFinish();
		m_CurrentFrameStartMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME);
		if (m_SetNewPreset)
			SetCurrentPreset(m_NextPresetIndex, false);

		DoColor();
		DoExposure();
		DoRotation();
		//glutPostRedisplay();

		m_IFS.Draw(m_OriginalPreset, m_CurrentPreset, m_NewPreset, width, height);
		//If new preset is set at this exact moment it will be overwritten below and never switched. What to do? Locking? - Matt.
		m_NewPreset = false;
		//glFinish();
	}

	bool VaryColor() { return m_VaryColor; }
	void VaryColor(bool vary) { m_VaryColor = vary; }
	//void Colorful(float f) { m_VaryColor = false; m_Colorful = f; }
	
	//float Exposure() { return m_CurrentPreset->MapExposure(); }//Should perhaps allow direct readonly access to the presets.
	//void Exposure(float exposure) { m_CurrentPreset->MapExposure(exposure); }
	bool VaryExposure() { return m_VaryExposure; }
	void VaryExposure(bool vary) { m_VaryExposure = vary; m_LastExposureVaryMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME); }

	bool RotateX() { return m_RotateX; }
	void RotateX(bool rotate) { m_RotateX = rotate; m_LastXRotationMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME); }
	
	bool RotateY() { return m_RotateY; }
	void RotateY(bool rotate) { m_RotateY = rotate; m_LastYRotationMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME); }
	
	bool RotateO() { return m_RotateO; }
	void RotateO(bool rotate) { m_RotateO = rotate; m_LastORotationMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME); }

	float XRotationRate() { return m_XRotationRate; }
	void XRotationRate(float rate) { m_XRotationRate = rate; }

	float YRotationRate() { return m_YRotationRate; }
	void YRotationRate(float rate) { m_YRotationRate = rate; }

	float ORotationRate() { return m_ORotationRate; }
	void ORotationRate(float rate) { m_ORotationRate = rate; }

	void XformRotationMult(unsigned int index, float mult) { if (index < m_XformRotMult.size()) m_XformRotMult[index] = mult; }
	float XformRotationMult(unsigned int index) { return index < m_XformRotMult.size() ? m_XformRotMult[index] : 0.0f; }

	int CurrentPresetIndex() { return m_CurrentPresetIndex; }
	PresetList GetPresetList() { return m_PresetList; }
	Preset* CurrentPreset() { return m_CurrentPreset; }

	void NextPreset(unsigned int presetIndex, bool merge)
	{
		//m_Merge = merge;
		m_NextPresetIndex = presetIndex;
		m_SetNewPreset = true;
	}

private:

	/// <summary>
	/// This should be the only place that a preset is ever changed since it's not only responsible
	/// for setting the preset index, but also for instantiating a new preset object to be used for
	/// current rendering calculations. It also frees the previous one.
	/// </summary>
	/// <param name="presetIndex">The new index to use</param>
	void SetCurrentPreset(unsigned int presetIndex, bool merge)
	{
		if (m_SetNewPreset)
		{
			//Clamp to valid range, assign to member after all setup is done.
			presetIndex = presetIndex < PresetList::Size() ? presetIndex : 0;

			/*if (merge)
			{
				m_NextPresetQueue.push_back(*(m_PresetVector[m_CurrentPresetIndex]));
				m_LastMergeMsec = glutGet(GLUT_ELAPSED_TIME);
			}
			else
			{*/
				//if (!m_Merge)
				//	m_NextPresetQueue.clear();//If they were doing any merging, abort it.

				if (m_CurrentPreset)
					delete m_CurrentPreset;

				if (m_OriginalPreset)
					delete m_OriginalPreset;
			
				//It's important to understand how these are used.
				//OriginalPreset is a copy of the one at m_CurrentPresetIndex in the master list, PresetList.
				//CurrentPrest is what's actually being used to draw.
				//When rotating for animations, the rotation is applied to the original preset and stored in 
				//the current preset for drawing.
				//However, when directly editing values, they will be applied to the original preset, which the
				//current preset will continue to rotate.
				//If the user wants to get back to the original, unmodified preset, they will have to leave it then
				//come back. Perhaps could do some sort of reset function.
				m_CurrentPreset = new Preset(*(PresetList::Get(presetIndex)));
				m_OriginalPreset = new Preset(*(PresetList::Get(presetIndex)));

				//Reset all values.
				m_XRotationAngle = 0.0f;
				m_YRotationAngle = 0.0f;
				m_ORotationAngle = 0.0f;
				m_XformRotMult.clear();

				for (unsigned int i = 0; i < m_OriginalPreset->Size(); i++)
					m_XformRotMult.push_back((float)i + 1.0f);

				m_LastXRotationMsec = m_LastYRotationMsec = m_LastORotationMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME);
			//}

			m_SetNewPreset = false;
			m_CurrentPresetIndex = presetIndex;
			m_NewPreset = true;
		}
	}

	void DoColor()
	{
		if (m_VaryColor && (m_CurrentFrameStartMsec - m_LastColorVaryMsec) > m_ColorTimePerXform)
		{
			static float colorVaryDirection = 1.0f;

			if (m_CurrentPreset->Colorful() > m_ColorMax)
			{
				colorVaryDirection = -1.0f;
			}
			else if (m_CurrentPreset->Colorful() < m_ColorMin)
			{
				colorVaryDirection = 1.0f;
			}

			m_CurrentPreset->Colorful(m_CurrentPreset->Colorful() + (m_ColorVaryRate * colorVaryDirection));
			m_LastColorVaryMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME);
		}
	}

	void DoExposure()
	{
		if (m_VaryExposure && (m_CurrentFrameStartMsec - m_LastExposureVaryMsec) > m_ExposureTimePerXform)
		{
			static float exposureVaryDirection = 1.0f;
			bool negativeExposure = m_OriginalPreset->Exposure() < 0;

			//Find out the ranges from the original.
			m_ExposureMax = negativeExposure ? m_OriginalPreset->Exposure() : 2.0f;
			m_ExposureMin = negativeExposure ? -2.0f : m_OriginalPreset->Exposure();

			//Apply the adjustment to the current, within range.
			if (m_CurrentPreset->Exposure() > m_ExposureMax)
			{
				exposureVaryDirection = -1.0f;
			}
			else if (m_CurrentPreset->Exposure() < m_ExposureMin)
			{
				exposureVaryDirection = 1.0f;
			}
			
			m_CurrentPreset->Exposure(m_CurrentPreset->Exposure() + (m_ExposureVaryRate * exposureVaryDirection));
			m_LastExposureVaryMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME);
		}
	}

	void DoRotation()
	{
		if (m_RotateX || m_RotateY || m_RotateO)
		{
			float sX, cX, sY, cY, sO, cO;
			float xRateMult, yRateMult, oRateMult;

			//Need to be able to specify rotation rate for each axis on each transform.
			//Axes should all rotate the same, but each transform should rotate slightly differently.

			if (m_RotateX)
			{
				xRateMult = (float)(m_CurrentFrameStartMsec - m_LastXRotationMsec) / 1000.0f;
			}

			if (m_RotateY)
			{
				yRateMult = (float)(m_CurrentFrameStartMsec - m_LastYRotationMsec) / 1000.0f;
			}

			if (m_RotateO)
			{
				oRateMult = (float)(m_CurrentFrameStartMsec - m_LastORotationMsec) / 1000.0f;
			}

			for (unsigned int m = 0; m < m_CurrentPreset->Size(); m++)
			{
				if (m < m_OriginalPreset->Size())//Original and current may be different when merging. Fix later - Matt.
				{
					xForm& originalTransform = m_OriginalPreset->Transform(m);//These will be references to the actual vector elements.
					xForm& currentTransform = m_CurrentPreset->Transform(m);
				
					//Note that we went from doing current.rotated = original.original * rotation to
					//current.rotated = current.original * rotation. Eventually resolve which is most convenient after
					//implementing a GUI editor.
					if (m_RotateX)
					{
						m_XRotationAngle += m_XRotationRate * xRateMult * m_XformRotMult[m];
						m_XRotationAngle = fmod((float)m_XRotationAngle, (float)(2.0 * M_PI));
						//printf("m_XRotationAngle = %f\n", m_XRotationAngle);
						sX = (float)sin(m_XRotationAngle);
						cX = (float)cos(m_XRotationAngle);
						currentTransform.RotatedX((currentTransform.X() * cX) - (currentTransform.Y() * sX));
						m_LastXRotationMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME);
					}
					else
					{
						//This logic is somewhat flawed in that it will only apply if at least one axis is rotating.
						//Which prevents us from doing edits while it's still. 
						//It also has the side effect of when you start and stop rotation, it resets to the original
						//preset value. Perhaps change later.
						currentTransform.RotatedX(currentTransform.X());
					}

					if (m_RotateY)
					{
						m_YRotationAngle += m_YRotationRate * yRateMult * m_XformRotMult[m];
						m_YRotationAngle = fmod((float)m_YRotationAngle, (float)(2.0 * M_PI));
						sY = (float)sin(m_YRotationAngle);
						cY = (float)cos(m_YRotationAngle);
						currentTransform.RotatedY((currentTransform.X() * sY) + (currentTransform.Y() * cY));//Multiply them to produce our newly rotated transform and assign.
						m_LastYRotationMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME);
					}
					else
					{
						currentTransform.RotatedY(currentTransform.Y());
					}

					if (m_RotateO)//Rotoating O gives more widely varied results in some cases.
					{
						m_ORotationAngle += m_ORotationRate * oRateMult * m_XformRotMult[m];
						m_ORotationAngle = fmod((float)m_ORotationAngle, (float)(2.0 * M_PI));
						sO = (float)sin(m_ORotationAngle);
						cO = (float)cos(m_ORotationAngle);
						currentTransform.RotatedO(currentTransform.O() * sO);
						m_LastORotationMsec = (unsigned __int64)glutGet(GLUT_ELAPSED_TIME);
					}
					else
					{
						currentTransform.RotatedO(currentTransform.O());
					}

					//currentTransform.o = originalTransform.o * c;
					//currentTransform.o = originalTransform.o * s - originalTransform.o * c;
					//currentTransform.o = originalTransform.o * s + originalTransform.o * c;
					//currentTransform.o = originalTransform.o * c - originalTransform.o * s;
					//currentTransform.o = originalTransform.o * c + originalTransform.o * s;
				}
			}
		}
	}

	//Timing.
	unsigned __int64 m_CurrentFrameStartMsec;

	//Rotation.
	bool m_RotateX;
	bool m_RotateY;
	bool m_RotateO;
	bool m_RotateXClockwise;
	bool m_RotateYClockwise;
	bool m_RotateOClockwise;
	float m_XRotationRate;
	float m_YRotationRate;
	float m_ORotationRate;
	float m_XRotationAngle;
	float m_YRotationAngle;
	float m_ORotationAngle;
	unsigned __int64 m_LastXRotationMsec;
	unsigned __int64 m_LastYRotationMsec;
	unsigned __int64 m_LastORotationMsec;
	vector<float> m_XformRotMult;

	//Color.
	bool m_VaryColor;
	int m_ColorTimePerXform;
	float m_ColorVaryRate;
	float m_ColorMax;
	float m_ColorMin;
	//float m_Colorful;
	float m_ColorVaryDirection;
	unsigned __int64 m_LastColorVaryMsec;

	//Exposure.
	bool m_VaryExposure;
	unsigned __int64 m_LastExposureVaryMsec;
	int m_ExposureTimePerXform;
	float m_ExposureMax;
	float m_ExposureMin;
	float m_ExposureVaryRate;

	//Preset.
	bool m_NewPreset;
	bool m_SetNewPreset;
	int m_NextPresetIndex;
	unsigned int m_CurrentPresetIndex;
	Preset* m_OriginalPreset;//Need to ditch current vs original and just make it one original with rotated vectors.
	Preset* m_CurrentPreset;
	VariationList m_VariationList;//Must come before preset list to instantiate static list.
	PresetList m_PresetList;

	//IFS.
	IfsRenderer m_IFS;
};
}